﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk.Workflow;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Crm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using System.Threading;

/**
 * Copyright 2011 by Alberto Gemin
 * agemin@hotmail.com
 * Version 1.7.0.1
 * 13 Dec 2011
 **/
namespace Gemina.CRM2011.WF.LdgManager
{
    [AgCodeActivity(ActivityNameSetFilterTemplateDefault, AgViewsUtils.GroupName)]
    public class AgSetFilterTemplateDefault : AgCodeActivityBase
    {

        #region Constants

        private const string ActivityNameSetFilterTemplateDefault = "Set Filter Template Default";
        private const string Param1InputViewId = "Filter Template Id";
        private const string Param2InputSavedQuery = "Filter Template";
        private const string Param3InputMakeDefault = "True: Set Default, False: Reset Default";
        private const string MsgMultipleDefault = "Invalid state of the database: multiple default queries exists.";
        private const string MsgInvalidSavedView = "Invalid system view for this operation.";
        private const string MsgInvalidTemplate = "Operation invalid for query type {0}. Setting default template is only valid for query types {1} and {2}.";
        private const string MsgBothNull = "{0} and {1} cannot be both null.";
        private const string MsgBothNotNull = "{0} and {1} cannot be both populated.";

        #endregion

        #region Properties

        public string ValidatedViewId { get; set; }

        #endregion

        #region Workflow Input Parameters

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param1InputViewId)]
        public InArgument<String> InputViewId { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param2InputSavedQuery)]
        [ReferenceTarget(AgViewsUtils.EntityNameSavedQuery)]
        public InArgument<EntityReference> InputSavedQuery { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param3InputMakeDefault)]
        [RequiredArgument]
        [Default("True")]
        public InArgument<bool> InputMakeDefault { get; set; }

        #endregion

        #region Properties

        public bool ValidatedSetDefault { get; set; }

        #endregion

        #region CodeActivity

        protected override void Execute(CodeActivityContext executionContext)
        {
            // We need to wrap this call within a critical section, to avoid deadlock timeouts
            Semaphore sem = new Semaphore(1, 1, typeof(AgDeploySystemView).ToString());
            try
            {
                sem.WaitOne();
                base.Execute(executionContext);
            }
            catch
            {
                throw;
            }
            finally
            {
                sem.Release();
            }
        }

        override protected bool ExecuteBody(CodeActivityContext executionContext)
        {
            Guid templateId = this.CheckArguments(executionContext);
            Entity template = this.GetTemplate(executionContext, templateId);

            bool isDefault = (bool)template.Attributes[AgViewsUtils.FieldIsDefault];
            if (isDefault)
            {
                if (!this.ValidatedSetDefault)
                {
                    this.SetResetIsDefault(executionContext, template.Id, false);
                }
            }
            else
            {
                if (this.ValidatedSetDefault)
                {
                    this.ReassignDefault(executionContext, template);
                }
            }

            return true;
        }

        #endregion

        #region Local Methods

        private EntityCollection FindOtherDefaults(CodeActivityContext executionContext, Entity template)
        {
            QueryExpression query = new QueryExpression
            {
                EntityName = AgViewsUtils.EntityNameSavedQuery,
                ColumnSet = new ColumnSet(AgViewsUtils.FieldIsDefault),
                Criteria = new FilterExpression
                {
                    Conditions = 
                    {
                        new ConditionExpression
                        {
                            AttributeName = AgViewsUtils.FieldQueryType,
                            Operator = ConditionOperator.Equal,
                            Values = { template.Attributes[AgViewsUtils.FieldQueryType] }
                        },
                        new ConditionExpression
                        {
                            AttributeName = AgViewsUtils.FieldReturnedTypeCode,
                            Operator = ConditionOperator.Equal,
                            Values = { template.Attributes[AgViewsUtils.FieldReturnedTypeCode] }
                        },
                        new ConditionExpression
                        {
                            AttributeName = AgViewsUtils.FieldSavedQueryId,
                            Operator = ConditionOperator.NotEqual,
                            Values = { template.Id }
                        },
                        new ConditionExpression
                        {
                            AttributeName = AgViewsUtils.FieldIsDefault,
                            Operator = ConditionOperator.Equal,
                            Values = { true }
                        }
                    }
                }
            };

            return this.GetService(executionContext).RetrieveMultiple(query);
        }

        private void SetResetIsDefault(CodeActivityContext executionContext, Guid id, bool value)
        {
            Entity newEntity = new Entity(AgViewsUtils.EntityNameSavedQuery);
            newEntity.Id = id;
            newEntity.Attributes.Add(AgViewsUtils.FieldIsDefault, value);
            this.GetService(executionContext).Update(newEntity);
        }

        private void ReassignDefault(CodeActivityContext executionContext, Entity template)
        {
            EntityCollection defaultTemplates = this.FindOtherDefaults(executionContext, template);
            if (defaultTemplates.Entities.Count == 0)
            {
                // there is no default
                this.SetResetIsDefault(executionContext, template.Id, true);
            }
            else if (defaultTemplates.Entities.Count > 1)
            {
                throw new AgErrorMessage(MsgMultipleDefault);
            }
            else
            {
                // there is another template set as default
                try
                {
                    this.SetResetIsDefault(executionContext, defaultTemplates[0].Id, false);
                    this.SetResetIsDefault(executionContext, template.Id, true);
                }
                catch
                {
                    // in case something bad happens we try to undo
                    this.SetResetIsDefault(executionContext, defaultTemplates[0].Id, true);
                    throw;
                }
            }
        }

        private Entity GetTemplate(CodeActivityContext executionContext, Guid templateId)
        {
            Entity template = this.GetService(executionContext).Retrieve(AgViewsUtils.EntityNameSavedQuery, templateId,
                new ColumnSet(AgViewsUtils.FieldQueryType, AgViewsUtils.FieldIsDefault, AgViewsUtils.FieldReturnedTypeCode));

            if (!template.Attributes.Contains(AgViewsUtils.FieldQueryType) || !template.Attributes.Contains(AgViewsUtils.FieldIsDefault) ||
                !template.Attributes.Contains(AgViewsUtils.FieldReturnedTypeCode))
            {
                throw new AgErrorMessage(MsgInvalidSavedView);
            }

            SavedQueryQueryTypeEnum queryType = (SavedQueryQueryTypeEnum)template.Attributes[AgViewsUtils.FieldQueryType];
            if (queryType != SavedQueryQueryTypeEnum.OfflineTemplate && queryType != SavedQueryQueryTypeEnum.OutlookTemplate)
            {
                throw new AgErrorMessage(string.Format(MsgInvalidTemplate, queryType, SavedQueryQueryTypeEnum.OfflineTemplate, SavedQueryQueryTypeEnum.OutlookTemplate));
            }

            return template;
        }

        private Guid CheckArguments(CodeActivityContext executionContext)
        {
            this.ValidatedViewId = this.InputViewId.Get(executionContext);
            EntityReference validatedSavedQuery = this.InputSavedQuery.Get(executionContext);
            bool validatedIsSavedQuery = (validatedSavedQuery != null);
            this.ValidatedSetDefault = this.InputMakeDefault.Get(executionContext);

            if (!string.IsNullOrEmpty(this.ValidatedViewId))
            {
                if (validatedIsSavedQuery)
                {
                    throw new AgErrorMessage(string.Format(MsgBothNotNull, Param1InputViewId, Param2InputSavedQuery));
                }
                return new Guid(this.ValidatedViewId);
            }
            else
            {
                if (!validatedIsSavedQuery)
                {
                    throw new AgErrorMessage(string.Format(MsgBothNull, Param1InputViewId, Param2InputSavedQuery));
                }
                return validatedSavedQuery.Id;
            }
        }

        #endregion

    }
}
